package com.xinhe.web.util;

import javax.crypto.Cipher;
import java.io.*;
import java.lang.reflect.Method;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

public class RSAUtils {
    private static final String PKCS8_PUBLIC = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1SVXdu51Sh/9HsBNZpjE\n5bKvRGS3NgUmNBLQVdI/imn/A3g4TobmmpGCExIuP6RkTQXgPG63h0KpmuOaa6mQ\nwe7j/+j5knwsILJ+AXWCiK541rFN+ykMNWL/Sbyl7X3UG9tT+82pmzx1FjJwkD8+\nHdaYBOHWrH1w6SQNZhEQm9DfyE/XpADUF++CwvmkhFsTkHnYHzUfNJV/VwS5DqoK\nPLEdr6qvfhZ83lb0ozY8/fun5ZO3nuUgAykR4gDToC1ZuGLdq+y74IJ+mE+R2g0J\nq/E1UT4Nif1x/IQEyhWzbXeq6kfkE8xCVRfW6NLOI0XiPmFPGslbBpo+qQicNjcL\nCwIDAQAB";

    public static byte[] decrypt(byte[] encryptedBytes, PrivateKey privateKey, int keyLength, int reserveSize, String cipherAlgorithm) throws Exception {
        int keyByteSize = keyLength / 8;
        int decryptBlockSize = keyByteSize - reserveSize;
        int nBlock = encryptedBytes.length / keyByteSize;
        ByteArrayOutputStream outbuf = null;
        try {
            Cipher cipher = Cipher.getInstance(cipherAlgorithm);
            cipher.init(2, privateKey);

            outbuf = new ByteArrayOutputStream(nBlock * decryptBlockSize);
            int offset;
            for (offset = 0; offset < encryptedBytes.length; offset += keyByteSize) {
                int inputLen = encryptedBytes.length - offset;
                if (inputLen > keyByteSize) {
                    inputLen = keyByteSize;
                }
                byte[] decryptedBlock = cipher.doFinal(encryptedBytes, offset, inputLen);
                outbuf.write(decryptedBlock);
            }
            outbuf.flush();
            return outbuf.toByteArray();
        } catch (Exception e) {
            throw new Exception("DEENCRYPT ERROR:", e);
        } finally {
            try {
                if (outbuf != null) {
                    outbuf.close();
                }
            } catch (Exception e) {
                outbuf = null;
                throw new Exception("CLOSE ByteArrayOutputStream ERROR:", e);
            }
        }
    }

    public static byte[] encrypt(byte[] plainBytes, PublicKey publicKey, int keyLength, int reserveSize, String cipherAlgorithm) throws Exception {
        int keyByteSize = keyLength / 8;
        int encryptBlockSize = keyByteSize - reserveSize;
        int nBlock = plainBytes.length / encryptBlockSize;
        if (plainBytes.length % encryptBlockSize != 0) {
            nBlock++;
        }
        ByteArrayOutputStream outbuf = null;
        try {
            Cipher cipher = Cipher.getInstance(cipherAlgorithm);
            cipher.init(1, publicKey);

            outbuf = new ByteArrayOutputStream(nBlock * keyByteSize);
            int offset;
            for (offset = 0; offset < plainBytes.length; offset += encryptBlockSize) {
                int inputLen = plainBytes.length - offset;
                if (inputLen > encryptBlockSize) {
                    inputLen = encryptBlockSize;
                }
                byte[] encryptedBlock = cipher.doFinal(plainBytes, offset, inputLen);
                outbuf.write(encryptedBlock);
            }
            outbuf.flush();
            return outbuf.toByteArray();
        } catch (Exception e) {
            throw new Exception("ENCRYPT ERROR:", e);
        } finally {
            try {
                if (outbuf != null) {
                    outbuf.close();
                }
            } catch (Exception e) {
                outbuf = null;
                throw new Exception("CLOSE ByteArrayOutputStream ERROR:", e);
            }
        }
    }

    public static PrivateKey getPriKey(String privateKeyPath, String keyAlgorithm) {
        PrivateKey privateKey = null;
        InputStream inputStream = null;
        try {
            if (inputStream == null) {
                System.out.println("hahhah1!");
            }

            inputStream = new FileInputStream(privateKeyPath);
            System.out.println("hahhah2!");
            privateKey = getPrivateKey(inputStream, keyAlgorithm);
            System.out.println("hahhah3!");
        } catch (Exception e) {
            System.out.println("加载私钥出错!");
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (Exception e) {
                    System.out.println("加载私钥,关闭流时出错!");
                }
            }
        }
        return privateKey;
    }


    public static PublicKey getPublicKey(String keyAlgorithm) throws Exception {
        try {
            X509EncodedKeySpec pubX509 = new X509EncodedKeySpec(decodeBase64("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1SVXdu51Sh/9HsBNZpjE\n5bKvRGS3NgUmNBLQVdI/imn/A3g4TobmmpGCExIuP6RkTQXgPG63h0KpmuOaa6mQ\nwe7j/+j5knwsILJ+AXWCiK541rFN+ykMNWL/Sbyl7X3UG9tT+82pmzx1FjJwkD8+\nHdaYBOHWrH1w6SQNZhEQm9DfyE/XpADUF++CwvmkhFsTkHnYHzUfNJV/VwS5DqoK\nPLEdr6qvfhZ83lb0ozY8/fun5ZO3nuUgAykR4gDToC1ZuGLdq+y74IJ+mE+R2g0J\nq/E1UT4Nif1x/IQEyhWzbXeq6kfkE8xCVRfW6NLOI0XiPmFPGslbBpo+qQicNjcL\nCwIDAQAB"));


            System.out.println("b5.........");
            KeyFactory keyFactory = KeyFactory.getInstance(keyAlgorithm);
            System.out.println("b6.........");

            PublicKey publicKey = keyFactory.generatePublic(pubX509);
            System.out.println("b7.........");
            return publicKey;
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("b8.........");
            throw new Exception("READ PUBLIC KEY ERROR:", e);
        }
    }


    public static PrivateKey getPrivateKey(InputStream inputStream, String keyAlgorithm) throws Exception {
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
            StringBuilder sb = new StringBuilder();
            String readLine = null;
            while ((readLine = br.readLine()) != null) {
                if (readLine.charAt(0) == '-') {
                    continue;
                }
                sb.append(readLine);
                sb.append('\r');
            }

            System.out.println("hahhah4!" + decodeBase64(sb.toString()));
            PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(decodeBase64(sb.toString()));
            System.out.println("hahhah5!");
            KeyFactory keyFactory = KeyFactory.getInstance(keyAlgorithm);
            System.out.println("hahhah6!");
            PrivateKey privateKey = keyFactory.generatePrivate(priPKCS8);
            System.out.println("hahhah7!");
            return privateKey;
        } catch (Exception e) {
            throw new Exception("READ PRIVATE KEY ERROR:", e);
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
            } catch (IOException e) {
                inputStream = null;
                throw new Exception("INPUT STREAM CLOSE ERROR:", e);
            }
        }
    }

    public static String encodeBase64(byte[] input) throws Exception {
        Class<?> clazz = Class.forName("com.sun.org.apache.xerces.internal.impl.dv.util.Base64");
        Method mainMethod = clazz.getMethod("encode", new Class[]{byte[].class});
        mainMethod.setAccessible(true);
        Object retObj = mainMethod.invoke((Object) null, new Object[]{input});
        return (String) retObj;
    }


    public static byte[] decodeBase64(String input) throws Exception {
        Class<?> clazz = Class.forName("com.sun.org.apache.xerces.internal.impl.dv.util.Base64");
        Method mainMethod = clazz.getMethod("decode", new Class[]{String.class});
        mainMethod.setAccessible(true);
        Object retObj = mainMethod.invoke((Object) null, new Object[]{input});
        return (byte[]) retObj;
    }
}
